- 
          
- 
        Couldn't load subscription status. 
- Fork 514
Improve detection of the type from a PHP variable #2814
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|  | ||
| This pattern can be adapted for any custom class—just implement the conversion logic for your value object. | ||
|  | ||
| .. |FQCN| raw:: html <abbr title="Fully-Qualified Class Name">FQCN</abbr> | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@GromNaN: this looks like it may be a false positive, since this is some very niche RST syntax
d446495    to
    d106a5a      
    Compare
  
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review. I added the type detection for DTO class name in property type.
|  | ||
| This pattern can be adapted for any custom class—just implement the conversion logic for your value object. | ||
|  | ||
| .. |FQCN| raw:: html <abbr title="Fully-Qualified Class Name">FQCN</abbr> | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I revert changes on this line. I'm not sure how this will be formatted.
| By using the |FQCN| of the value object class as the type name, the type is | ||
| automatically used when encountering a property of that class. This means you | ||
| can omit the ``type`` option when defining the field mapping:: | ||
|  | ||
| .. code-block:: php | ||
| #[Field] | ||
| public Ramsey\Uuid\Uuid $id; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a new feature that can compete with embedded documents: adding custom logic to convert a value object into BSON.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the example should be with a custom class instead, since UUID are new supported using symfony/uid #2826
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't have a global namespace for exception like it is generally done in libraries. Existing exception names are:
- Doctrine\ODM\MongoDB\MongoDBException
- Doctrine\ODM\MongoDB\ConfigurationException
- Doctrine\ODM\MongoDB\DocumentNotFoundException
- Doctrine\ODM\MongoDB\Mapping\MappingException
- Doctrine\ODM\MongoDB\Hydrator\HydratorException
- Doctrine\ODM\MongoDB\LockException
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could introduce a marker interface similar to how we have MongoDB\Driver\Exception\Exception in the extension.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR improves type detection for PHP variables in the MongoDB ODM by enhancing the getTypeFromPHPVariable method to automatically detect custom types based on object class names. The change allows custom types registered using their FQCN to be automatically selected when processing aggregation expressions.
Key changes:
- Enhanced type detection to check if an object's class name corresponds to a registered custom type
- Improved exception handling by introducing a dedicated InvalidTypeException
- Added automatic type inference for object properties when custom types are registered with class FQCNs
Reviewed Changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description | 
|---|---|
| lib/Doctrine/ODM/MongoDB/Types/Type.php | Enhanced getTypeFromPHPVariablemethod to detect custom types by class name and improved type detection logic | 
| lib/Doctrine/ODM/MongoDB/Types/InvalidTypeException.php | New dedicated exception class for invalid type scenarios | 
| lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php | Added automatic type detection for properties with registered custom types | 
| tests/Doctrine/ODM/MongoDB/Tests/Types/TypeTest.php | Added comprehensive test coverage for the new type detection functionality | 
| tests/Doctrine/ODM/MongoDB/Tests/Functional/CustomTypeTest.php | Added tests demonstrating custom type detection and proper test setup/teardown | 
| tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH2789Test.php | Improved test isolation by properly managing type registry state | 
| docs/en/reference/custom-mapping-types.rst | Updated documentation with UUID custom type example and FQCN usage patterns | 
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
| return $value; | ||
| } | ||
| if ($value instanceof Binary) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to restrict this to a MongoDB\BSON\Binary where getType() returns either TYPE_OLD_UUID or TYPE_UUID? As-is, it's not clear what would happen if you encountered binary data from the server with a different sub-type.
I'll note that convertToDatabaseValue() yields a Binary object with TYPE_UUID, so perhaps there's no reason to accept TYPE_OLD_UUID here.
If you are intentionally being liberal in what you accept and conservative in what you emit (Robustness Principle) and would like to leave this as-is, that's fine by me. You can optionally add a comment here explaining that design decision for the benefit of readers.
| return null; | ||
| } | ||
| if ($value instanceof Binary) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you want to ensure you always yield a Binary with sub-type TYPE_UUID? If so, you might need to split this into two separate conditionals.
if ($value instanceof Binary && $value->getType() !== Binary::TYPE_UUID) {
    // Note: this may throw if the data is not exactly 16 bytes
    return new Binary($value->getBytes(), Binary::TYPE_UUID);
}
if ($value instanceof Binary) {
    return $value;
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather simplify the example.
https://github.com/doctrine/mongodb-odm/pull/2814/files#r2393846289
| if (null === $value || [] === $value) { | ||
| return null; | ||
| } | ||
| if ($value instanceof Binary) { | ||
| return $value; | ||
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can simplify the example and remove this edge cases.
| if (null === $value || [] === $value) { | |
| return null; | |
| } | |
| if ($value instanceof Binary) { | |
| return $value; | |
| } | |
| if (null === $value) { | |
| return null; | |
| } | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without the $value instanceof Binary check, wouldn't the method then throw InvalidArgumentException for a Binary value? Or are you comfortable assuming that a Binary would never be passed in?
I didn't follow why [] was originally being handled alongside null, so I don't have much thought on that change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the example of document class, the property has the ?\Ramsey\Uuid\Uuid type, which ensure the RamseyUuidType::convertToDatabaseValue() method never get anything else.
As soon as the field value is the database is not corrupted, the simple implementation will work perfectly. No need to add support for edge cases that are never encountered.
| return null; | ||
| } | ||
| if ($value instanceof Binary) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd rather simplify the example.
https://github.com/doctrine/mongodb-odm/pull/2814/files#r2393846289
| By using the |FQCN| of the value object class as the type name, the type is | ||
| automatically used when encountering a property of that class. This means you | ||
| can omit the ``type`` option when defining the field mapping:: | ||
|  | ||
| .. code-block:: php | ||
| #[Field] | ||
| public Ramsey\Uuid\Uuid $id; | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the example should be with a custom class instead, since UUID are new supported using symfony/uid #2826
840f7b8    to
    f0ecc10      
    Compare
  
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll defer to @alcaeus to sign off on this.
| if ($variable instanceof ObjectId) { | ||
| return self::getType(self::ID); | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I must check why this could be removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In fact, this type detection is invalid when the property type is ObjectId:
- the IdTypeclass does nothing as the target type is alsoObjectId
- the IdTypeclass convert theObjectIdreceived from the Database intostring, which is invalid when assigning the value to theObjectIdproperty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes LGTM, but I would prefer changing out the UUID example.
| <field field-name="field" type="date_with_timezone" /> | ||
| Custom Type Example: Mapping a UUID Class | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With UUID merged, WDYT of using moneyphp as an example?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated the tutorial.
| return $mapping; | ||
| } | ||
|  | ||
| if (! $type->isBuiltin() && Type::hasType($type->getName())) { | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm not mistaken, this would allow us to use a FQCN as identifier for a type name to automatically link values of that class with a given type? I haven't even thought of that before, but it makes so much sense!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could introduce a marker interface similar to how we have MongoDB\Driver\Exception\Exception in the extension.
61cf72e    to
    f751500      
    Compare
  
    9857b15    to
    a90d2ba      
    Compare
  
    There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM with two suggestions for the docs
| .. code-block:: php | ||
| #[Field(type: \Money\Money::class)] | ||
| public ?\Money\Money $price; | ||
| By using the |FQCN| of the value object class as the type name, the type is | ||
| automatically used when encountering a property of that class. This means you | ||
| can omit the ``type`` option when defining the field mapping:: | 
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would remove the first example and explain that the mapping logic automatically detects the property type and uses it as field type.
3a7b1bf    to
    6cd2006      
    Compare
  
    
Summary
The method
getTypeFromPHPVariableis used only byconvertPHPToDatabaseValue. Which is used to process expressions in the aggregation builder.With this new feature, we try to find the type for object class name; assuming custom types are registered using the FQCN of the objects they store.